package com.onpositive.knowledge.model.impl;

import java.io.Serializable;
import java.util.Arrays;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.knowledge.model.inference.MergedProperty;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.MPProvider;
import com.onpositive.semantic.model.api.meta.BaseMeta;
import com.onpositive.semantic.model.api.meta.DefaultMetaKeys;
import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IHasPropertyProvider;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.semantic.model.api.property.PropertyAccess;
import com.onpositive.semantic.model.api.property.java.JavaPropertyProvider;
import com.onpositive.semantic.model.api.property.java.annotations.Id;
import com.onpositive.semantic.model.api.realm.IRealmProvider;
import com.onpositive.units.ParsedSection;

public abstract class BasicUniverseProperty implements IProperty, Serializable,IHasPropertyProvider {

	protected transient MergedProperty lowLevelProperty = new MergedProperty();
	protected transient IProperty userAdjustmentProperty;

	public abstract BasicUniverse getUniverse();

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime
				* result
				+ ((userAdjustmentProperty == null) ? 0
						: userAdjustmentProperty.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BasicUniverseProperty other = (BasicUniverseProperty) obj;
		if (userAdjustmentProperty == null) {
			if (other.userAdjustmentProperty != null)
				return false;
		} else if (!userAdjustmentProperty.equals(other.userAdjustmentProperty))
			return false;
		return true;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	protected BaseMeta meta = new BaseMeta() {

		public void putMeta(String key, Object object) {
			super.putMeta(key, object);
			changed( getId(), key, null );
		};
	};
	
	protected BaseMeta auditMeta = new BaseMeta() {

		public void putMeta(String key, Object object) {
			super.putMeta(key, object);
			changed(getId(),key,"audit");			
		};
	};
	{
		meta.setParentMeta(auditMeta) ;
	}

	public BasicUniverseProperty(IProperty userAjustment, BaseMeta m) {
		super();
		this.userAdjustmentProperty = userAjustment;
		
		if (m != null) {
			for (Object s : m.declaredKeys()) {
				String string = s.toString();
				Object singleValue = m.getSingleValue(string, Object.class,
						null);
				meta.putMeta(string, singleValue);
			}
		}		
		meta.registerService(IRealmProvider.class,new MPProvider());
		meta.setDefaultMeta(userAjustment.getMeta());
		meta.putMeta(DefaultMetaKeys.ID_KEY, userAjustment.getId());
		auditMeta.putMeta(DefaultMetaKeys.ID_KEY, userAjustment.getId());
		
	}

	protected void changed(String string,String key,String tag) {
		PropertyAccess.firePropertyStructureListener(this);
	}

	@Override
	public IMeta getMeta() {
		return meta;
	}

	@Override
	@Id
	public String getId() {
		return DefaultMetaKeys.getStringValue(this, DefaultMetaKeys.ID_KEY);
	}

	@Override
	public Object getValue(Object obj) {
		Object value = userAdjustmentProperty.getValue(obj);
		if (value != null) {
			if (value instanceof DescribedValue) {
				return value;
			}
			// set by user;
			return new DescribedValue(value, "Set manually");
		}
		value = lowLevelProperty.getValue(obj);
		DescribedValue[] describedValues = DescribedValue.toDescribedValues( value, "low level");
		DescribedValue[] adjust = getUniverse().getAuditManager().adjust(obj, this, describedValues);
		// commutative

		// transitive

		// inverse
		if (adjust != null) {
			if (adjust.length == 1) {
				return adjust[0];
			}
			if (adjust.length == 0) {
				return null;
			}
			Object[] vls = new Object[adjust.length];
			int a = 0;
			StringBuilder comment = new StringBuilder();
			int min = Byte.MAX_VALUE;
			long ts = Long.MIN_VALUE;
			for (DescribedValue v : adjust) {

				Object commentObj = v.getComment();
				if( commentObj != null ){
					if ( comment.length() > 0 )
						comment.append(", ");
					
					comment.append( commentObj.toString() );					
				}
				vls[a++] = v;
				ts = Math.min(ts, v.getTimestamp() );
				min = Math.max(v.getInferred(), min);
			}
			return new DescribedValue( (byte) min, Arrays.asList(vls), null, ts, comment.toString() );
		}
		return adjust;
	}
	
	public void copyParentMeta(BaseMeta m) {
		
		if (m != null) {
			for (Object s : m.declaredKeys()) {
				String string = s.toString();
				Object singleValue = m.getSingleValue(string, Object.class,	null);
				auditMeta.putMeta(string, singleValue);
			}
		}		
	}

	public void merge(IProperty p) {
		if (auditMeta.getParentMeta() == null) {
			auditMeta.setParentMeta(p.getMeta());
		}
		lowLevelProperty.add(p);
	}

	public void remove(IProperty p) {
		lowLevelProperty.remove(p);
		if (lowLevelProperty.isEmpty()){
			auditMeta.setParentMeta(null);
		}
	}

	public boolean isEmpty() {
		return lowLevelProperty.isEmpty() && meta.declaredKeys().isEmpty();
	}

	@Override
	public String toString() {
		return userAdjustmentProperty.getId();
	}
	
	public boolean isDepricated(){
		return DefaultMetaKeys.getValue(this, IPropertyConstants.CHAR_DEPRICATED_LABEL, Boolean.class,false);
	}
	public boolean isSuitableForTables(){
		Object value = DefaultMetaKeys.getValue(this, IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class,null);
		if (value!=null){
			if (value.equals(ParsedSection.ID)){
				return false;
			}
			if (value.equals("file-list")){
				return false;
			}
			if (value.equals("string")){
				return false;
			}
			return true;
		}
		return false;
	}
	
	public PropertyTypeImpl getType(){
		String pvalue = DefaultMetaKeys.getValue(this, IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class);
		String value = DefaultMetaKeys.getValue(this, IPropertyConstants.CHAR_PRIMARY_UNIT_LABEL, String.class);
		if (pvalue==null){
			return new PropertyTypeImpl("undef");
		}
		return new PropertyTypeImpl(pvalue+(value!=null?(":"+value):""));
	}
	

	@Override
	public IPropertyProvider getPropertyProvider() {
		return JavaPropertyProvider.instance;
	}

	
}
